/*
 * Copyright (C) 2011, Pavel Samko <bulldozerbsg@gmail.com>
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the "Pavel Samko" nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

#include "VCModWaveOut.h"

#if !VC_PLATFORM_WIN
#error ONLY FOR WINDOWS!
#endif

#pragma comment(lib, "winmm.lib")

VCModWaveOutThread::VCModWaveOutThread(VCModWaveOut & mod)
: VCThread()
, m_mod(mod)
, m_needstate(WORK_INIT)
, m_state(WORK_NONE)
, m_device(NULL)
{
}

int VCModWaveOutThread::Execute(void * data)
{
	while (!m_mod.m_needexit)
	{
		VCAutoMutex lock(m_device_mutex);
		if ((m_state != WORK_ERROR) && (m_needstate != WORK_NONE) && (m_needstate != m_state))
		{
			STATE needstate = m_needstate;
			m_needstate = WORK_NONE;
			switch(needstate)
			{
			case WORK_INIT:
				if ((m_state == WORK_NONE) && !m_device)
				{
					WAVEFORMATEX format;
					format.wFormatTag = WAVE_FORMAT_PCM;
					format.nChannels = 1;
					format.nSamplesPerSec = 32000;
					format.nAvgBytesPerSec = 32000 * 2;
					format.nBlockAlign = 2;
					format.wBitsPerSample = 16;
					format.cbSize = 0;
					MMRESULT r = waveOutOpen(&m_device, WAVE_MAPPER, &format, 0, 0, CALLBACK_NULL);
					if (r == MMSYSERR_NOERROR)
					{
						m_buff_1_hdr.lpData = (LPSTR)m_buff_1;
						m_buff_1_hdr.dwBufferLength = VCMODWAVEOUT_BUFF_SIZE * sizeof(short int);
						m_buff_1_hdr.dwBytesRecorded = 0;
						m_buff_1_hdr.dwUser = 0;
						m_buff_1_hdr.dwFlags = 0;
						m_buff_1_hdr.dwLoops = 0;

						m_buff_2_hdr.lpData = (LPSTR)m_buff_2;
						m_buff_2_hdr.dwBufferLength = VCMODWAVEOUT_BUFF_SIZE * sizeof(short int);
						m_buff_2_hdr.dwBytesRecorded = 0;
						m_buff_2_hdr.dwUser = 0;
						m_buff_2_hdr.dwFlags = 0;
						m_buff_2_hdr.dwLoops = 0;

						m_buff_curr = 1;

						m_mod.m_controller.OnNotifyFromMod(m_mod, VC_INFO_INIT);
						m_state = WORK_STREAM;
						TRACE("wave out init ok")
					} else
					{
						m_mod.m_controller.OnNotifyFromMod(m_mod, VC_ERROR_INIT);
						m_state = WORK_ERROR;
						TRACE("wave out init error")
					}
				}
				break;
			default:
				break;
			}
		} else
		{
			if ((m_state == WORK_ERROR) && (m_needstate != WORK_NONE))
			{
				m_mod.m_controller.OnNotifyFromMod(m_mod, VC_ERROR);
				m_needstate = WORK_NONE;
			} else
			{
				switch(m_state)
				{
				case WORK_STREAM:
					{
						VCAutoMutex lock(m_mod.m_buff_mutex);
						if (m_mod.m_buff.Size() > VCMODWAVEOUT_WAIT_SIZE)
						{
							m_buff_curr = 1;
							m_state = WORK_STREAM_PLAY;
						}
					}
					break;
				case WORK_STREAM_PLAY:
					switch (m_buff_curr)
					{
					case 1:
						if (waveOutUnprepareHeader(m_device, &m_buff_1_hdr, sizeof(WAVEHDR)) != WAVERR_STILLPLAYING)
						{
							VCAutoMutex lock(m_mod.m_buff_mutex);
							size_t play_size = (m_mod.m_buff.Size() < (VCMODWAVEOUT_BUFF_SIZE * sizeof(short int))) ? m_mod.m_buff.Size() : (VCMODWAVEOUT_BUFF_SIZE * sizeof(short int));
							if (play_size)
							{
								m_mod.m_buff.Pop(m_buff_1, play_size);
								m_buff_1_hdr.dwBufferLength = play_size;

								waveOutPrepareHeader(m_device, &m_buff_1_hdr, sizeof(WAVEHDR));
								waveOutWrite(m_device, &m_buff_1_hdr, sizeof(WAVEHDR));
							} else
							{
								m_state = WORK_STREAM_FINISHPLAY;
							}
							m_buff_curr = 2;
						}
					case 2:
						if (waveOutUnprepareHeader(m_device, &m_buff_2_hdr, sizeof(WAVEHDR)) != WAVERR_STILLPLAYING)
						{
							VCAutoMutex lock(m_mod.m_buff_mutex);
							size_t play_size = (m_mod.m_buff.Size() < (VCMODWAVEOUT_BUFF_SIZE * sizeof(short int))) ? m_mod.m_buff.Size() : (VCMODWAVEOUT_BUFF_SIZE * sizeof(short int));
							if (play_size)
							{
								m_mod.m_buff.Pop(m_buff_2, play_size);
								m_buff_2_hdr.dwBufferLength = play_size;

								waveOutPrepareHeader(m_device, &m_buff_2_hdr, sizeof(WAVEHDR));
								waveOutWrite(m_device, &m_buff_2_hdr, sizeof(WAVEHDR));
							} else
							{
								m_state = WORK_STREAM_FINISHPLAY;
							}
							m_buff_curr = 1;
						}
						break;
					default:
						break;
					}
					break;
				case WORK_STREAM_FINISHPLAY:
					switch (m_buff_curr)
					{
					case 1:
						if (waveOutUnprepareHeader(m_device, &m_buff_1_hdr, sizeof(WAVEHDR)) != WAVERR_STILLPLAYING)
						{
							m_state = WORK_STREAM;
						}
					case 2:
						if (waveOutUnprepareHeader(m_device, &m_buff_2_hdr, sizeof(WAVEHDR)) != WAVERR_STILLPLAYING)
						{
							m_state = WORK_STREAM;
						}
						break;
					default:
						break;
					}
					break;
				default:
					break;
				}
			}
		}
		VCSleep(1);
	}
	return 0;
}

void VCModWaveOutThread::OnExit(int exitcode)
{
	VCAutoMutex lock(m_device_mutex);
	if (m_device)
	{
		if ((m_state == WORK_STREAM_PLAY) || (m_state == WORK_STREAM_FINISHPLAY)) // abnormal situation
		{
			waveOutReset(m_device);

			waveOutUnprepareHeader(m_device, &m_buff_1_hdr, sizeof(WAVEHDR));
			waveOutUnprepareHeader(m_device, &m_buff_2_hdr, sizeof(WAVEHDR));
		}

		waveOutClose(m_device);
		m_device = NULL;
	}
}

VC_RESULT VCModWaveOutThread::SetNeedState(STATE needstate)
{
	VC_RESULT result = VC_FALSE;
	if (m_needstate != WORK_INIT)
	{
		m_needstate = needstate;
		result = VC_OK;
	}
	return result;
}

VCModWaveOut::VCModWaveOut(VCIController & controller)
: VCIMod(controller)
, m_needexit(false)
{
	m_thread = new VCModWaveOutThread(*this);
}

VCModWaveOut::~VCModWaveOut()
{
	m_needexit = true;
	if (m_thread)
	{
		m_thread->Wait();
		delete m_thread;
	}
}

VC_RESULT VCModWaveOut::Push(VC_CMD cmd, void * data_buff, size_t data_size, VC_DATAFORMAT data_format)
{
	VC_RESULT result = VC_FALSE;
	switch(cmd)
	{
	case VC_STREAM_BEGIN:
	case VC_STREAM:
	case VC_STREAM_END:
		if (data_format == VC_PCM_1_32000_S16)
		{
			if (data_buff && data_size)
			{
				VCAutoMutex lock(m_buff_mutex);
				m_buff.Push(data_buff, data_size);
			}
			result = VC_OK;
		}
		break;
	default:
		break;
	}
	return result;
}

void VCModWaveOut::SetVolume(unsigned char volume)
{
	if (m_thread)
	{
		VCAutoMutex lock(m_thread->m_device_mutex);
		if (m_thread->m_device)
		{
			DWORD vol = MAKELONG((WORD)volume << 8, (WORD)volume << 8);
			waveOutSetVolume(m_thread->m_device, vol);
		}
	}
}

unsigned char VCModWaveOut::GetVolume()
{
	if (m_thread)
	{
		DWORD vol;
		VCAutoMutex lock(m_thread->m_device_mutex);
		if (waveOutGetVolume(m_thread->m_device, &vol) == MMSYSERR_NOERROR)
		{
			return (unsigned char)((vol & 0xFFFF) >> 8);
		}
	}
	return 0;
}

